//
// Created by tang on 2022/6/30.
//

#include "single_app.hpp"
#include <filesystem>
#include <cassert>
#include <utils/path_tools.hpp>
#include "core/assert.hpp"
#include "fmt/format.h"
#include "utils/single_app.hpp"

#if _WIN32
#include <Shlwapi.h>
#include <Windows.h>
#include <cassert>

#pragma comment(lib,"Shlwapi.lib")

inline std::wstring fromUtf8(const std::string &input) {
    const int len = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, nullptr, 0);
    std::wstring out_wide_string;
    out_wide_string.resize(len + 1);
    out_wide_string[len] = 0;
    MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &out_wide_string[0], len);
    out_wide_string.resize(len - 1);
    return out_wide_string;
}

inline std::string toUtf8(const std::wstring &input) {
    int length = WideCharToMultiByte(CP_UTF8, 0, input.data(), -1, nullptr, 0, nullptr, nullptr);
    std::string out_string;
    out_string.resize(length + 1);
    length = WideCharToMultiByte(CP_UTF8, 0, input.data(), -1, &out_string[0], length, nullptr, nullptr);
    out_string[length] = 0;
    out_string.resize(length - 1);
    return out_string;
}

#else
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <cstring>
#endif

struct SingleAppHelper::MyData {
    bool        is_self {true};
    std::string pid_file_path;
#if _WIN32
    HANDLE hMutex {nullptr};
    HANDLE hPidFile {nullptr};
#else
    int               fd_pid {0};
    sem_t            *sem{nullptr};
#endif
    uint32_t     pid {0};

    static std::string  app_id;

    static void SetId (const char *id);

    void Init ();

    void Clean ();
};

std::string SingleAppHelper::MyData::app_id{};

void SingleAppHelper::MyData::Init ()
{
    std::string folder = GetTempFilePath(app_id.c_str());
    pid_file_path = fmt::format("{}/instance.pid", folder);
#if _WIN32
    std::wstring w_folder = fromUtf8(folder);
    if (!(CreateDirectoryW(w_folder.c_str(), NULL) || GetLastError() == ERROR_ALREADY_EXISTS))
    {
        // TODO: Error handling
    }

    wchar_t buff[1024] {0};
    std::wstring MutexName = L"ApplicationInstance/";
    MutexName += fromUtf8(app_id);

    auto PidPath = fromUtf8 (pid_file_path);

    // try open mutex
    const HANDLE mtx = OpenMutexW (MUTEX_ALL_ACCESS, FALSE, MutexName.c_str ());
    if (mtx == nullptr)
    {
        // first instance
        hMutex = CreateMutexW (nullptr, TRUE, MutexName.c_str ());
        // 第一个实例，写pid文件
        hPidFile = CreateFileW (PidPath.c_str (), GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
        assert (hPidFile != INVALID_HANDLE_VALUE);
        pid               = GetCurrentProcessId ();
        DWORD      writen = 0;
        const BOOL r      = WriteFile (hPidFile, &pid, sizeof (pid), &writen, nullptr);
        assert (r);
    }
    else
    {
        is_self  = false;
        hMutex   = mtx;
        hPidFile = CreateFileW (PidPath.c_str (), GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                                nullptr);

        if (hPidFile != INVALID_HANDLE_VALUE)
        {
            DWORD read;
            BOOL  r = ReadFile (hPidFile, &pid, sizeof (pid), &read, nullptr);
            assert (r);
        }
    }
#else
    struct stat st;
    if (stat(folder.c_str(), &st) == -1) {
        if (0 != mkdir(folder.c_str(), 0777))
        {
            perror(folder.c_str());
            abort();
        }
    }
    fd_pid                          = open (pid_file_path.data (), O_CREAT | O_RDWR, 0600);
    if (fd_pid < 0)
    {
        perror (pid_file_path.data ());
        abort ();
    }

    // lock file
    struct flock lock {
    };
    memset (&lock, 0x0, sizeof (lock));
    lock.l_type = F_WRLCK;
    int ret     = fcntl (fd_pid, F_SETLK, &lock);
    if (ret >= 0)
    {
        // success
        is_self = true;
        pid     = (uint32_t) getpid ();

        lseek (fd_pid, 0, SEEK_SET);
        ssize_t written = write (fd_pid, &pid, sizeof (pid));
        PI_ASSERT (written == sizeof (pid));
        fsync (fd_pid);
    }
    else
    {
        // failed
        is_self = false;

        lseek (fd_pid, 0, SEEK_SET);
        ssize_t r = read (fd_pid, &pid, sizeof (pid));
        PI_ASSERT (r == sizeof (pid));
    }
#endif
}

void SingleAppHelper::MyData::Clean ()
{
#if _WIN32
    CloseHandle (hPidFile);
    CloseHandle (hMutex);
#else
    if (fd_pid > 0)
    {
        if (is_self)
        {  // 是本程序
            struct flock lock {
            };
            memset (&lock, 0x0, sizeof (lock));
            lock.l_type = F_UNLCK;
            fcntl (fd_pid, F_SETLK, &lock);  // 解锁文件

            unlink (pid_file_path.data ());  // 设置关闭时删除
        }
        close (fd_pid);
        fd_pid = -1;  // 关闭文件
    }
#endif
}

void SingleAppHelper::MyData::SetId(const char *app_id) {
    assert(app_id);
    if (MyData::app_id.empty()) {
        MyData::app_id = std::string{app_id};
    }
}

bool SingleAppHelper::CheckRunning ()
{
    return !Instance ()->d->is_self;
}

uint32_t SingleAppHelper::ProcessId ()
{
    return Instance ()->d->pid;
}

SingleAppHelper::SingleAppHelper () : d {new MyData}
{
    d->Init ();
}

SingleAppHelper::~SingleAppHelper ()
{
    d->Clean ();
    delete d;
}

SingleAppHelper *SingleAppHelper::Instance ()
{
    static SingleAppHelper instance {};
    return &instance;
}

void SingleAppHelper::SetAppId(const char *id) {
    MyData::SetId(id);
}
